#ifndef __MY_SERVER_HPP__
#define __MY_SERVER_HPP__

#include <iostream>
#include <functional>
#include "data.hpp"
#include "view.hpp"
#include "httplib.h"

extern cloud::DataManager *_data;

namespace cloud
{
    /*借助cpp-httplib库搭建网络通信服务*/
    class Server
    {
    private:
        /*上传文件*/
        void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1.数据在正文部分，有file字段标识
            auto iter = req.has_file("file");
            if (iter == false)
            {
                rsp.status = 400;
                return;
            }

            // 2.存在正文部分，获取正文数据
            const auto &file = req.get_file_value("file");

            /*
                获得的file就是这个类型，字段都被填充好了
                struct MultipartFormData {
                std::string name;
                std::string content;//暂时只用到这个字段
                std::string filename;//和这个字段
                std::string content_type;
                };
            */

            // 3.将正文内容写入文件
            Config *cfg = Config::GetInstance();
            std::string back_dir = cfg->GetBackDir();
            std::string file_path = back_dir + FileUtil(file.filename).FileName(); // 形成文件路径
            FileUtil fu(file_path);
            fu.SetContent(file.content);

            // 4.更新BackupInfo信息
            BackupInfo info;
            info.NewBackupInfo(file_path);
            _data->Insert(info);
            return;
        }

        /*获取文件信息列表*/
        void ListShow(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1.获取所有BackupInfo信息
            std::vector<BackupInfo> arry;
            _data->GetAll(&arry);

            // 2.组织正文格式
            std::string html;
            _view.AllExpandHtml(arry, &html);
            rsp.set_content(html, "text/html");
            rsp.status = 200;
            return;
        }

        /*用于Download函数中，对于压缩文件进行解压缩，对于解压后的文件同时放到./backdir目录下*/
        void UnCompress(BackupInfo &info)
        {
            std::string pack_path=info._pack_path;
            std::string real_path=info._real_path;
            FileUtil fu(pack_path);
            fu.UnCompress(real_path);
            info._pack_flag=false;
            fu.Remove();
            _data->Update(info);
        }

        /*获取当前文件的Etag值，Etag值组成为：filename-fsize-mtime*/
        std::string GetEtag(const BackupInfo &info)
        {
            std::string str=FileUtil(info._real_path).FileName();
            str+='-';
            str+=std::to_string(info._fsize);
            str+='-';
            str+=std::to_string(info._mtime);
            return str;
        }

        /*下载文件*/
        void Download(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1.根据url获取BackupInfo
            BackupInfo info;
            _data->GetOneByURL(req.path, &info);

            // 2.判断文件是否被压缩,是，解压缩，否，正常读取
            if (info._pack_flag == true)
            {
                // 3.解压缩
                UnCompress(info);
            }

            // 4.判断是否是断点续传
            bool retrans = false;
            if (req.has_header("If-Range")) // 请求头部字段中If-Range是断点续传标志
            {
                std::string old_etag = req.get_header_value("If-Range"); // 获取If-Range的value值
                // 只有当old_etag与现在文件的etag相同时，证明文件没有被修改，才支持断点续传
                if (old_etag == GetEtag(info))
                    retrans = true;
            }

            // 5.针对是否是断点续传，采取不同的读取策略
            FileUtil fu(info._real_path);
            if (retrans == false)
            {
                // 正常下载
                fu.GetContent(&rsp.body);
                //6.设置响应头部字段：ETag    Accept-Ranges: bytes
                rsp.set_header("Accept-Ranges","bytes");
                rsp.set_header("ETag",GetEtag(info));
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.status = 200;
            }
            else
            {
                // 断点下载
                fu.GetContent(&rsp.body);
                //6.设置响应头部字段：ETag    Accept-Ranges: bytes
                //httplib库本身就是支持了断点下载，只需要我们将文件中的内容读取出来，httplib库中就会帮助我们根据断点区间将内容取出进行响应
                rsp.set_header("Accept-Ranges","bytes");
                rsp.set_header("ETag",GetEtag(info));
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.status = 206;
            }
        }

    public:
        /*完成一些初始化的工作 */
        Server()
        {
            Config *cfg = Config::GetInstance();
            _server_port = cfg->GetServerPort();
            _server_ip = cfg->GetServerIp();
            _download_prefix = cfg->GetDownloadPrefix();
        }

        /*设置各种回调函数，启动Server服务*/
        bool RunModule()
        {
            _server.Get("/listshow", std::bind(&Server::ListShow, this, std::placeholders::_1, std::placeholders::_2));
            _server.Get("/", std::bind(&Server::ListShow, this, std::placeholders::_1, std::placeholders::_2));
            _server.Post("/upload", std::bind(&Server::Upload, this, std::placeholders::_1, std::placeholders::_2));
            std::string download_url = _download_prefix;
            download_url += "(.*)";
            _server.Get(download_url, std::bind(&Server::Download, this, std::placeholders::_1, std::placeholders::_2));
            _server.listen(_server_ip.c_str(), _server_port);
            return true;
        }

    private:
        uint16_t _server_port;        // 服务器port
        std::string _server_ip;       // 服务器ip
        std::string _download_prefix; // 下载前缀路径
        View _view;                   // view渲染对象
        httplib::Server _server;      // Server服务
    };
}

#endif